package com.leonardo.persistenceme.model;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Vector;

import javax.microedition.rms.InvalidRecordIDException;
import javax.microedition.rms.RecordEnumeration;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStoreNotOpenException;

import com.leonardo.persistenceme.Class2Handler;
import com.leonardo.persistenceme.PersistGraphController;
import com.leonardo.persistenceme.exceptions.AlreadyPersistedException;
import com.leonardo.persistenceme.exceptions.DatabaseOpenException;
import com.leonardo.persistenceme.exceptions.DatabaseOperationException;
import com.leonardo.persistenceme.metadatahandlers.MetadataRepository;
import com.leonardo.persistenceme.metadatahandlers.PersistenceMetadataContainer;
import com.leonardo.persistenceme.model.filters.ClassFilter;
import com.leonardo.persistenceme.model.persistancehandlers.MetadataPersistenceHandler;
import com.leonardo.persistenceme.model.persistancehandlers.PersistenceHandler;

public class PersistenceManagerImpl implements PersistenceManager {

	private static RecordStore rs; 
	private String storeId;
	
	public PersistenceManagerImpl(String storeId) throws DatabaseOpenException {
		this.storeId = storeId;
		rs = getRs();
	}
		
/* DEFAULT METHODS */
	
	public Integer persist(Entity entity) throws DatabaseOperationException {
		return persist(entity, Class2Handler.getDefaultHandlerInstance());
	}
	
	public boolean offer(Entity entity) throws DatabaseOperationException {
		return offer(entity, Class2Handler.getDefaultHandlerInstance());
	}
	
	public Entity getByKey(Integer key) throws DatabaseOperationException {
		return getByKey(key, Class2Handler.getDefaultHandlerInstance());
	}

	public void update(Entity entity) throws DatabaseOperationException {
		update(entity, Class2Handler.getDefaultHandlerInstance());
	}

	
	public Vector list(Filter filter) throws DatabaseOperationException {
		return list(filter, Class2Handler.getDefaultHandlerInstance());
	}

	public Vector listAll() throws DatabaseOperationException {
		{/*System.out.println("Listing all");*/}
		return list((Filter) null, Class2Handler.getDefaultHandlerInstance());
	}
	
	public Vector list(Class clazz) {
		{/*System.out.println("Retrieving all entities of class " + clazz.getName());*/}
		return list(new ClassFilter(clazz), Class2Handler.getDefaultHandlerInstance());
	}
	
	public void deleteAll(Filter filter) throws DatabaseOperationException {
		deleteAll(filter, Class2Handler.getDefaultHandlerInstance());		
	}
	
	public void deleteAll(Class clazz) throws DatabaseOperationException {
		deleteAll(new ClassFilter(clazz), Class2Handler.getDefaultHandlerInstance());
	}

/* METHODS WITH EXTERNAL PERSISTENCE HANDLER */
	
	private Vector toPersist = new Vector();
	
	private Integer realPersist(Entity entity, PersistenceHandler handler) throws DatabaseOperationException {
		{/*System.out.println("Persisting with " + handler.getClass().getName());*/}
		try {
			initToPersist();
			PersistGraphController.addEntity(entity, getRs().getNextRecordID());
			handler.setAsRoot(storeId);
			handler.prePersist(entity);
			byte[] record = serialize(entity, handler);
			
			toPersist.insertElementAt(record, entity.getKey().intValue());
			
			handler.postPersist(entity);
			return entity.getKey();
		} catch (IOException e) {
			e.printStackTrace();
			throw new DatabaseOperationException(e);
		} catch (RecordStoreException e) {
			e.printStackTrace();
			throw new DatabaseOperationException(e);
		} catch (AlreadyPersistedException e) {
			return entity.getKey();
		} finally {
			if(PersistGraphController.endEntity())
				persistAll();
		}
	}
	
	public Integer persist(Entity entity, PersistenceHandler handler) throws DatabaseOperationException {
		entity.setKey(null);
		return realPersist(entity, handler);
	}
	
	private void initToPersist() throws RecordStoreException {
		int size = toPersist.size();
		for(int i = 0; i <= PersistGraphController.maxKey(); i++)
			if(i >= size)
				toPersist.addElement(null);
	}
	
	private void persistAll() {
		for(int i = 0; i < toPersist.size(); i++) {
			try {
				byte[] record = (byte[]) toPersist.elementAt(i);
				if(record == null)
					continue;
				
				if(i >= getRs().getNextRecordID())
					getRs().addRecord(record, 0, record.length);
				else
					getRs().setRecord(i, record, 0, record.length);
			} catch(RecordStoreException ex) {
				throw new DatabaseOperationException(ex);
			}
		}
		toPersist.removeAllElements();
	}
	
	public void update(Entity entity, PersistenceHandler handler) throws DatabaseOperationException {
		realPersist(entity, handler);
	}
	
	public boolean offer(Entity entity, PersistenceHandler handler)
			throws DatabaseOperationException {
		{/*System.out.println("Offering entity with key " + entity.getKey());*/}
		try {
			getRs().getRecord(entity.getKey().intValue());
		} catch (InvalidRecordIDException e) {
			persist(entity, handler);
			return true;
		} catch (RecordStoreException e) {
			e.printStackTrace();
			throw new DatabaseOperationException(e);
		}
		return false;
	}
	
	public Entity getByKey(Integer key, PersistenceHandler handler)
			throws DatabaseOperationException {
		if(key == null)
			throw new DatabaseOperationException("Entity cannot have null key");		
		try {
			handler.setAsRoot(storeId);
			handler.preLoad();
			byte[] record = getRs().getRecord(key.intValue());
			{/*System.out.println("Returning element with key " + key);*/}
			Entity loaded = deserialize(record, handler);
			handler.postLoad(loaded);
			return loaded;
		} catch (InvalidRecordIDException e) {
			e.printStackTrace();
			throw new DatabaseOperationException("No entity with key " + key);
		} catch (RecordStoreException e) {
			e.printStackTrace();
			throw new DatabaseOperationException(e);
		} catch (IOException e) {
			e.printStackTrace();
			throw new DatabaseOperationException(e);
		}
	}
	
	public Vector list(Filter filter, PersistenceHandler handler)
			throws DatabaseOperationException {
		{/*System.out.println("Getting list!");*/}
		try {
			RecordEnumeration it = getRs().enumerateRecords(null, null, false);
			Vector list = new Vector();
			handler.setAsRoot(storeId);
			while(it.hasNextElement()) {
				handler.preLoad();
				Entity entity = deserialize(it.nextRecord(), handler);
				if(filter != null && !filter.accept(entity))
					continue;
				handler.postLoad(entity);
				list.addElement(entity);
			}
			return list;
		} catch (IOException e) {
			e.printStackTrace();
			throw new DatabaseOperationException(e);
		} catch (RecordStoreException e) {
			e.printStackTrace();
			throw new DatabaseOperationException(e);
		}
	}
	
	public Vector listAll(PersistenceHandler handler)
			throws DatabaseOperationException {
		return list((Filter) null, handler);
	}
	
	public Vector list(Class clazz, PersistenceHandler handler)
			throws DatabaseOperationException {
		return list(new ClassFilter(clazz), handler);
	}
	
	public void deleteAll(Filter filter, PersistenceHandler handler)
			throws DatabaseOperationException {
		try {
			RecordEnumeration it = getRs().enumerateRecords(null, null, false);
			handler.setAsRoot(storeId);
			while(it.hasNextElement()) {
				Entity entity = deserialize(it.nextRecord(), handler);
				if(filter == null || filter.accept(entity)) {
					handler.preDelete(entity);
					getRs().deleteRecord(entity.getKey().intValue());
					handler.postDelete(entity);
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
			throw new DatabaseOperationException(e);
		} catch (RecordStoreException e) {
			e.printStackTrace();
			throw new DatabaseOperationException(e);
		}
	}

	public void deleteAll(Class clazz, PersistenceHandler handler)
			throws DatabaseOperationException {
		deleteAll(new ClassFilter(clazz), handler);
	}

/* METHODS USING METADATA */

	public Integer persist(Entity entity, String model)
			throws DatabaseOperationException {
		PersistenceMetadataContainer container = 
			MetadataRepository.getInstace().getContainer(model);
		return persist(entity, new MetadataPersistenceHandler(container));
	}
	
	public boolean offer(Entity entity, String model)
			throws DatabaseOperationException {
		PersistenceMetadataContainer container = 
			MetadataRepository.getInstace().getContainer(model);
		return offer(entity, new MetadataPersistenceHandler(container));
	}

	public Entity getByKey(Integer key, String model)
			throws DatabaseOperationException {
		PersistenceMetadataContainer container = 
			MetadataRepository.getInstace().getContainer(model);
		return getByKey(key, new MetadataPersistenceHandler(container));
	}
	
	public void update(Entity entity, String model)
			throws DatabaseOperationException {
		PersistenceMetadataContainer container = 
			MetadataRepository.getInstace().getContainer(model);
		update(entity, new MetadataPersistenceHandler(container));
	}

	public Vector list(Filter filter, String model)
			throws DatabaseOperationException {
		PersistenceMetadataContainer container = 
			MetadataRepository.getInstace().getContainer(model);
		return list(filter, new MetadataPersistenceHandler(container));
	}

	public Vector listAll(String model) throws DatabaseOperationException {
		PersistenceMetadataContainer container = 
			MetadataRepository.getInstace().getContainer(model);
		return list((Filter) null, new MetadataPersistenceHandler(container));
	}
	
	public Vector list(Class clazz, String model)
			throws DatabaseOperationException {
		PersistenceMetadataContainer container = 
			MetadataRepository.getInstace().getContainer(model);
		return list(new ClassFilter(clazz), new MetadataPersistenceHandler(container));
	}
	
	public void deleteAll(Filter filter, String model) throws DatabaseOperationException {
		PersistenceMetadataContainer container = 
			MetadataRepository.getInstace().getContainer(model);
		deleteAll(filter, new MetadataPersistenceHandler(container));
	}
	
	public void deleteAll(Class clazz, String model) throws DatabaseOperationException {
		PersistenceMetadataContainer container = 
			MetadataRepository.getInstace().getContainer(model);
		deleteAll(new ClassFilter(clazz), new MetadataPersistenceHandler(container));
	}

	
	/* GENERAL METHODS*/

	private RecordStore getRs() throws DatabaseOperationException {
		if(rs == null)
			try {
				rs = RecordStore.openRecordStore(storeId, true);
			} catch (RecordStoreException e) {
				throw new DatabaseOperationException("Problems opening database");
			}
		return rs;
	}

	public Integer numPersistedEntities() {
		try {
			return new Integer(getRs().getNumRecords());
		} catch (RecordStoreNotOpenException e) {
			throw new DatabaseOperationException(e);
		}
	}

	private byte[] serialize(Entity entity, PersistenceHandler handler) throws IOException, RecordStoreException {
		ByteArrayOutputStream bias = null;
		DataOutputStream stream = null;
		try {
			bias = new ByteArrayOutputStream();
			stream = new DataOutputStream(bias);
			handler.persist(entity, stream);
			byte[] record = bias.toByteArray();
			return record;
		} finally {
			try {
				bias.close();
				stream.close();
			} catch(Exception e) {
				e.printStackTrace();
			}
		}	
	}

	private Entity deserialize(byte[] record, PersistenceHandler handler) throws IOException {
		ByteArrayInputStream bias = null;
		DataInputStream stream = null;
		
		try {
			bias = new ByteArrayInputStream(record);
			stream = new DataInputStream(bias); 
			return (Entity) handler.recover(stream);
		} finally {
			try {
				bias.close();
				stream.close();
			} catch(Exception e) {
				e.printStackTrace();
			}
		}
	}

	public void delete(Integer key) throws DatabaseOperationException {
		{/*System.out.println("Deleting entity with key " + key);*/}
		try {
			getRs().deleteRecord(key.intValue());
		} catch (RecordStoreException e) {
			throw new DatabaseOperationException(e);
		} catch(NullPointerException e) {
			throw new DatabaseOperationException(e);
		}
	}

	public void clearDB() throws DatabaseOperationException {
		{/*System.out.println("Clearing DB " + storeId);*/}
		if(rs == null)
			return;
		try {
			try {
				rs.closeRecordStore();
			} catch(RecordStoreNotOpenException e) {
			}
			RecordStore.deleteRecordStore(storeId);
			rs = null;
		}
		catch (RecordStoreException e) {
			throw new DatabaseOperationException(e);
		}
	}
}